abstract class Mod
{
  WavePlayer wp;
  Gain gn;
  Glide gl;
  BiquadFilter bf;
  Panner pn;
  Reverb rvb;
  Add adder;
  ShortFrameSegmenter sfs;
  FFT fft;
  Mod[] mods;
  Mod parent;
  float bx, by, wx, wy, minWx, minWy;
  int index, ip, op, type, gx, gy, im, pgx, pgy, val, uid;
  inPort[] iPs;
  outPort[] oPs;
  outPort fIP;
  inPort fOP;
  String name;
  String img;
  String message;
  Boolean selected, isValue, hasIcon, isInput, isContainer, markedForDelete, root, alignRight, hideUnusedPorts, fIn, fOut;
  int stack, newStack;
  PImage icon;
  Mod(Mod tempParent, float tempBx, float tempBy, int tempIndex) {
    fIn=false;
    fOut=false;
    iPs=new inPort[255];
    oPs=new outPort[255];
    mods=new Mod[0];
    bx=tempBx;
    by=tempBy;
    index=tempIndex;
    parent=tempParent;
    selected=false;
    isValue=false;
    isInput=false;
    isContainer=false;
    hasIcon=false;
    markedForDelete=false;
    root=false;
    gx=0;
    gy=0;
    alignRight=false;
    hideUnusedPorts=false;
    op=0;
    ip=0;
    uid=uidNum;
    uidNum++;
  }
  Mod(int tempIndex) {
    fIn=false;
    fOut=false;
    iPs=new inPort[255];
    oPs=new outPort[255];
    mods=new Mod[0];
    bx=0;
    by=0;
    index=tempIndex;
    parent=this;
    selected=false;
    isValue=false;
    isInput=false;
    isContainer=false;
    hasIcon=false;
    markedForDelete=false;
    hideUnusedPorts=false;
    root=true;

    gx=0;
    gy=0;
  }

  void go() {
    //
  }

  void update(int tempI) {
    //
  }
  void destroy() {
    //
  }
  void callOnEdit() {
    //
  }

  void setWx(float tempWx) {
    if (alignRight) {
      bx=bx+wx-tempWx;
    }
    wx=tempWx;
    for (int i=0;i<op;i++) {
      oPs[i].bx=wx;
    }
  }
  void addNewPort(int tempI, int tempO) {
    if (tempO==0) {
      println(tempI);
      if (iPs[tempI].index-128==ip) {
        if (iPs[tempI].showIndex) {
          iPs[ip]=new inPort(this, iPs[tempI].type, str(ip+iPs[tempI].indexOff));
          iPs[ip-1].setShowIndex(true);
          iPs[ip-1].indexOff=iPs[tempI].indexOff;
        }
        else {
          iPs[ip]=new inPort(this, iPs[tempI].type, "");
        }
        iPs[ip-1].setShowUnused(true);
        iPs[ip-1].setAddPort(true);
      }
    }
    else {
      if (oPs[tempI].index==op) {
        if (oPs[tempI].showIndex) {
          oPs[op]=new outPort(this, oPs[tempI].type, str(op+oPs[tempI].indexOff));
          oPs[op-1].setShowIndex(true);
          oPs[op-1].indexOff=oPs[tempI].indexOff;
        }
        else {
          oPs[op]=new outPort(this, oPs[tempI].type, "");
        }
        oPs[op-1].setShowUnused(true);
        oPs[op-1].setAddPort(true);
      }
    }
    updatePorts();
  }
  void remPort(int tempI, int tempO) {
    if (tempO==0) {
      if (ip>1) {
        int i=ip-1;
        Boolean done=false;
        do {
          if (iPs[i].addPort && !iPs[i].isUsed && iPs[i-1].addPort && !iPs[i-1].isUsed) {
            ip--;
          }
          else {
            done=true;
          }
          i--;
        }
        while (!done && i>1);
      }
    }
    else {
      if (op>1) {
        int i=op-1;
        Boolean done=false;
        do {
          if (oPs[i].addPort && oPs[i].ws==0 && oPs[i-1].addPort && oPs[i-1].ws==0) {
            op--;
          }
          else {
            done=true;
          }
          i--;
        }
        while (!done && i>1);
      }
    }
    updatePorts();
  }
  void createFeedIn(Mod tp, int tempType) {
    fIP=new outPort(tp, tempType, "");
    fIP.index=128;
    fIP.feed=true;
    fIn=true;
    op--;
  }
  void createFeedOut(Mod tp, int tempType) {
    fOP=new inPort(tp, tempType, "");
    fOP.index=255;
    fOP.feed=true;
    fOut=true;
    ip--;
  }


  void loadImg(String img) {
    try {
      icon = loadImage("icons/"+img+".jpg");
      hasIcon=true;
    }
    catch(NullPointerException e) {
      hasIcon=false;
    }
  }
  void updatePorts() {
    int vip=0;
    int vop=0;
    if (parent.hideUnusedPorts) {
      for (int i=0;i<ip;i++) {
        if (iPs[i].isUsed)vip++;
      }
      for (int i=0;i<op;i++) {
        if (oPs[i].ws>0)vop++;
      }
    }
    else {
      vip=ip;
      vop=op;
    }
    if (vip<=1 && vop<=1) {
      wy=minWy;
    }
    else {
      wy=(1+max(vop, vip))*pGap;
    }
    if (!isValue) {
      wx=minWx;
    }
    int c=0;
    for (int i=0;i<ip;i++) {
      iPs[i].bx=0;
      if (parent.hideUnusedPorts) {
        if (iPs[i].isUsed) {
          iPs[i].by=wy*(c+1)/(vip+1);
          c++;
        }
      }
      else {
        iPs[i].by=wy*(i+1)/(vip+1);
      }
    }
    int d=0;
    for (int i=0;i<op;i++) {
      oPs[i].bx=wx;
      if (parent.hideUnusedPorts) {
        if (oPs[i].ws>0) {
          oPs[i].by=wy*(d+1)/(vop+1);
          d++;
        }
      }
      else {
        oPs[i].by=wy*(i+1)/(vop+1);
      }
    }
    if (fIn) {
      fIP.by=wy;
      fIP.bx=wx/2;
    }
    if (fOut) {
      fOP.by=wy;
      fOP.bx=wx/2;
    }
  }

  void drawMod(PGraphics canvas) {
    if (!markedForDelete) {

      canvas.fill(255);
      canvas.noStroke();
      canvas.rect(bx+r+pgx, by+pgy, wx-(r*2), r);
      canvas.rect(bx+pgx, by+r+pgy, wx, wy-(r*2));
      canvas.rect(bx+r+pgx, by+wy-r+pgy, wx-(r*2), r);

      if (selected) {
        canvas.stroke(150);
      }
      else {
        canvas.stroke(100);
      }
      canvas.strokeWeight(3);
      canvas.strokeCap(PROJECT);
      canvas.arc(bx+r+pgx, by+r+pgy, r*2, r*2, PI, TWO_PI-PI/2);
      canvas.line(bx+r+pgx, by+pgy, bx+wx-r+pgx, by+pgy);
      canvas.arc(bx+wx-r+pgx, by+r+pgy, r*2, r*2, TWO_PI-PI/2, TWO_PI);
      canvas.line(bx+wx+pgx, by+r+pgy, bx+wx+pgx, by+wy-r+pgy);
      canvas.arc(bx+wx-r+pgx, by+wy-r+pgy, r*2, r*2, 0, PI/2);
      canvas.line(bx+r+pgx, by+wy+pgy, bx+wx-r+pgx, by+wy+pgy);
      canvas.arc(bx+r+pgx, by+wy-r+pgy, r*2, r*2, PI/2, PI);
      canvas.line(bx+pgx, by+r+pgy, bx+pgx, by+wy-r+pgy);
      canvas.textAlign(CENTER);
      canvas.fill(64);
      canvas.text(name, bx+pgx+wx/2, by+pgy+12);
      // canvas.fill(255);
      //canvas.text(index, bx+pgx, by+pgy);
      if (hasIcon) {     
        try {

          canvas.image(icon, bx+pgx+(wx/2)-5, by+pgy+(wy/2)-5);
        }
        catch(NullPointerException e) {
        }
      }
      if (isContainer) {
        canvas.strokeWeight(1);
        canvas.stroke(150);
        canvas.noFill();
        float ar=8;
        canvas.arc(bx+pgx+(wx/2)-ar, by+pgy+(wy/2)-ar, ar, ar, PI, TWO_PI-PI/2);
        canvas.line(bx+pgx+(wx/2)-ar, by+pgy+(wy/2)-3*ar/2, bx+pgx+(wx/2)+ar, by+pgy+(wy/2)-3*ar/2);
        canvas.arc(bx+pgx+(wx/2)+ar, by+pgy+(wy/2)-ar, ar, ar, TWO_PI-PI/2, TWO_PI);
        canvas.line(bx+pgx+(wx/2)-ar, by+pgy+(wy/2)+3*ar/2, bx+pgx+(wx/2)+ar, by+pgy+(wy/2)+3*ar/2);
        canvas.arc(bx+pgx+(wx/2)+ar, by+pgy+(wy/2)+ar, ar, ar, 0, PI/2);
        canvas.line(bx+pgx+(wx/2)+3*ar/2, by+pgy+(wy/2)-ar, bx+pgx+(wx/2)+3*ar/2, by+pgy+(wy/2)+ar);
        canvas.arc(bx+pgx+(wx/2)-ar, by+pgy+(wy/2)+ar, ar, ar, PI/2, PI);
        canvas.line(bx+pgx+(wx/2)-3*ar/2, by+pgy+(wy/2)-ar, bx+pgx+(wx/2)-3*ar/2, by+pgy+(wy/2)+ar);

        if (selected) {
          canvas.stroke(150);
        }
        else {
          canvas.stroke(100);
        }
      }


      if (op!=0) {
        for (int i=op-1;i>=0;i--) {

          if (!(parent.hideUnusedPorts && oPs[i].ws==0)) {
            oPs[i].drawPort(canvas, bx+pgx, by+pgy, 0);
          }
        }
      }
      if (ip!=0) {
        for (int i=ip-1;i>=0;i--) {

          if (!(parent.hideUnusedPorts && !iPs[i].isUsed)) {
            iPs[i].drawPort(canvas, bx+pgx, by+pgy, 1);
          }
        }
      }
      if (fIn) {
        fIP.drawPort(canvas, bx+pgx, by+pgy, 0);
      }
      if (fOut) {
        fOP.drawPort(canvas, bx+pgx, by+pgy, 1);
      }
    }
  }
  void drawWires(PGraphics canvas) {

    canvas.noFill();
    canvas.strokeWeight(2);
    if (op!=0) {
      for (int i=0;i<op;i++) {
        if (oPs[i].ws!=0) {
          for (int j=0;j<oPs[i].ws;j++) {
            float thisX=bx+oPs[i].bx+pgx;
            float thisY=by+oPs[i].by+pgy;
            float endX=oPs[i].wires[j].end.parent.bx+oPs[i].wires[j].end.bx+pgx;
            float endY=oPs[i].wires[j].end.parent.by+oPs[i].wires[j].end.by+pgy;
            float cOff=abs(thisX-endX)/curveD+abs(thisY-endY)/curveD;
            if (oPs[i].wires[j].selected) {
              canvas.stroke(sysCl[oPs[i].type]);
              canvas.strokeWeight(5);
              canvas.strokeCap(ROUND);
              canvas.bezier(thisX+wO-5, thisY, thisX+cOff+wO, thisY, endX-cOff-wO, endY, endX-wO+5, endY);
            }
            canvas.stroke(sysCl[oPs[i].type]);
            canvas.strokeWeight(1.5);
            canvas.bezier(thisX+wO-1, thisY, thisX+cOff+wO, thisY, endX-cOff-wO, endY, endX-wO, endY);
          }
        }
      }
    }
    if (fIn) {
      if (fIP.ws!=0) {
        for (int j=0;j<fIP.ws;j++) {
          float thisX=bx+fIP.bx+pgx;
          float thisY=by+fIP.by+pgy;
          float endX=fIP.wires[j].end.parent.bx+fIP.wires[j].end.bx+pgx;
          float endY=fIP.wires[j].end.parent.by+fIP.wires[j].end.by+pgy;
          float cOff=abs(thisX-endX)/2;
          if (fIP.wires[j].selected) {
            canvas.stroke(sysCl[fIP.type]);
            canvas.strokeWeight(5);
            canvas.strokeCap(ROUND);
            //canvas.bezier(thisX, thisY+wO, thisX, thisY+cOff+wO, endX, endY+cOff+wO, endX, endY+wO);
          }
          else {
            canvas.stroke(sysCl[fIP.type]);
            canvas.strokeWeight(1.5);
          }
          if(thisY>endY){
            canvas.line(endX,endY,endX,thisY);
            canvas.bezier(thisX, thisY, thisX, thisY+cOff, endX, thisY+cOff, endX, thisY);
          }else{
            //canvas.stroke(255);
            canvas.line(thisX,thisY,thisX,endY);
            canvas.bezier(thisX, endY, thisX, endY+cOff, endX, endY+cOff, endX, endY);
          }
          
        }
      }
    }
  }
  void drawModClick(PGraphics click) {
    click.noSmooth();
    click.fill(index, 0, 0);
    click.noStroke();
    click.rect(bx+pgx, by+pgy, wx, wy);
    if (isContainer) {
      click.fill(index, 0, 1);
      click.rect(bx+pgx+(wx/2)-12, by+pgy+(wy/2)-12, 24, 24);
    }
    if (op!=0) {
      for (int i=op-1;i>=0;i--) {
        if (!(parent.hideUnusedPorts && oPs[i].ws==0)) {
          oPs[i].drawPortClick(click, bx+pgx, by+pgy, index);
        }
      }
    }
    if (ip!=0) {
      for (int i=ip-1;i>=0;i--) {
        if (!(parent.hideUnusedPorts && !iPs[i].isUsed)) {
          iPs[i].drawPortClick(click, bx+pgx, by+pgy, index);
        }
      }
    }
    if (fIn) {
      fIP.drawPortClick(click, bx+pgx, by+pgy, index);
    }
    if (fOut) {
      fOP.drawPortClick(click, bx+pgx, by+pgy, index);
    }
  }
  void drawWiresClick(PGraphics click) {
    click.noSmooth();
    click.noFill();

    click.strokeWeight(10);
    click.strokeCap(SQUARE);
    if (op!=0) {
      for (int i=0;i<op;i++) {
        if (oPs[i].ws!=0) {
          for (int j=0;j<oPs[i].ws;j++) {
            float thisX=bx+oPs[i].bx+pgx;
            float thisY=by+oPs[i].by+pgy;
            float endX=oPs[i].wires[j].end.parent.bx+oPs[i].wires[j].end.bx+pgx;
            float endY=oPs[i].wires[j].end.parent.by+oPs[i].wires[j].end.by+pgy;
            float cOff=abs(thisX-endX)/curveD+abs(thisY-endY)/curveD;
            click.stroke(index, i+1, j+1);
            click.bezier(thisX+wO-1, thisY, thisX+cOff+wO, thisY, endX-cOff-wO, endY, endX-wO, endY);
          }
        }
      }
    }
    if (fIn) {
      if (fIP.ws!=0) {
        for (int j=0;j<fIP.ws;j++) {
          float thisX=bx+fIP.bx+pgx;
          float thisY=by+fIP.by+pgy;
          float endX=fIP.wires[j].end.parent.bx+fIP.wires[j].end.bx+pgx;
          float endY=fIP.wires[j].end.parent.by+fIP.wires[j].end.by+pgy;
          float cOff=abs(thisX-endX)/(curveD*2)+abs(thisY-endY)/curveD;
          click.stroke(index, 128, j+1);
          click.bezier(thisX, thisY+wO, thisX, thisY+cOff+wO, endX, endY+cOff+wO, endX, endY+wO);
        }
      }
    }
  }
  void setB(float tempX, float tempY) {
    bx=tempX;
    by=tempY;
  }
  void setName(String tempName) {
    name=tempName;
  }

  void sendString(String message, int outPort) {
    for (int i=0;i<oPs[outPort].ws;i++) {
      oPs[outPort].wires[i].end.setString(message);
      oPs[outPort].wires[i].end.parent.update(oPs[outPort].wires[i].end.index);
    }
  }
  void sendFloat(Float message, int outPort) {
    for (int i=0;i<oPs[outPort].ws;i++) {
      oPs[outPort].wires[i].end.setFloat(message);
      oPs[outPort].wires[i].end.parent.update(oPs[outPort].wires[i].end.index);
    }
  }
  void beginStackAll() {
    for (int i=0;i<im;i++) {
      parent.mods[i].newStack=0;
    }
    for (int i=0;i<im;i++) {
      parent.mods[i].callStack(0);
    }
    for (int i=0;i<im;i++) {
      parent.mods[i].stack=parent.mods[i].newStack;
    }
  }
  void callStack(int count) {
    count++;
    if (newStack<count)newStack=count;
    for (int j=0;j<op;j++) {
      for (int i=0;i<oPs[j].ws;i++) {
        oPs[j].wires[i].end.parent.callStack(count);
      }
    }
  }
  void doStack() {
    Map<Object, Integer> event=new HashMap<Object, Integer>();
    Mod doNow=this;
    Set s=event.entrySet();
    int again=0;
    do {
      again--;
      for (int i=0;i<doNow.op;i++) {
        for (int j=0;j<doNow.oPs[i].ws;j++) {
          inPort trig=doNow.oPs[i].wires[j].end;
          if (/*trig.trigger && */!event.containsKey(new Integer(trig.parent.index))) {
            event.put(new Integer(trig.parent.index), new Integer(trig.parent.stack));
            again=2;
          }
        }
      }
      Iterator it=s.iterator();
      int nextLevel=0;
      int nextIndex=0;
      while (it.hasNext ())
      {
        Map.Entry m =(Map.Entry)it.next();
        int stackVal=(Integer)m.getValue();
        if (stackVal<nextLevel || nextLevel==0)
        {
          nextLevel=stackVal;
          nextIndex=(Integer)m.getKey();
        }
      }
      if (nextIndex>0) {
        doNow=parent.mods[nextIndex-1];
        doNow.go();
        event.remove(new Integer(nextIndex));
      }
    }
    while (!event.isEmpty ()||again>0);
  }
  Boolean isInChain(int tempI) {
    Map<Object, Integer> test=new HashMap<Object, Integer>();
    Mod doNow=this;
    Set s=test.entrySet();
    int again=0;
    Boolean found=false;
    do {
      again--;
      for (int i=0;i<doNow.op;i++) {
        for (int j=0;j<doNow.oPs[i].ws;j++) {
          inPort trig=doNow.oPs[i].wires[j].end;
          if (trig.parent.index==tempI) {
            found=true;
          }
          else {
            if (!test.containsKey(new Integer(trig.parent.index))) {
              test.put(new Integer(trig.parent.index), new Integer(trig.parent.stack));
              again=2;
            }
          }
        }
      }
      Iterator it=s.iterator();
      int nextLevel=0;
      int nextIndex=0;
      if (!found) {
        while (it.hasNext ())
        {
          Map.Entry m =(Map.Entry)it.next();
          int stackVal=(Integer)m.getValue();
          if (stackVal<nextLevel || nextLevel==0)
          {
            nextLevel=stackVal;
            nextIndex=(Integer)m.getKey();
          }
        }
        if (nextIndex>0) {
          doNow=parent.mods[nextIndex-1];
          test.remove(new Integer(nextIndex));
        }
      }
    }
    while ( (!test.isEmpty ()||again>0)&& !found);
    return(found);
  }
  void delMod() {
    for (int i=0;i<op;i++) {
      if (oPs[i].ws>0) {
        do {
          oPs[i].delWire(0);
          if (oPs[i].type==3) {
            oPs[i].plug.kill();
            oPs[i].plug=null;
          }
        }
        while (oPs[i].ws>0);
      }
    }
    for (int i=0;i<ip;i++) {
      if (iPs[i].isUsed) {
        iPs[i].inWire.start.delWire(iPs[i].inWire.index);
      }
      if (iPs[i].type==3) {
        iPs[i].plug.kill();
        iPs[i].plug=null;
      }
    }
    markedForDelete=true;
    if (isInput) {
      oscHandler[val].removeListener(this);
      //midi/serial
    }
    destroy();
  }
}

class modContainer extends Mod
{
  modContainer(Mod tempParent, float tempBx, float tempBy, int tempIndex) {
    super(tempParent, tempBx, tempBy, tempIndex);
    mods = new Mod[255];
    isContainer=true;
    img="ctn";
    loadImg(img);
    im=9;
    gx=0;
    gy=0;
    minWx=60;
    minWy=60;
    mods[0]=new oscInMod(this, 0, 0, 1);
    mods[1]=new mathMulMod(this, 100, 0, 2);
    mods[2]=new monomeMod(this, 200, 0, 3);
    mods[3]=new inspectMod(this, 300, 0, 4);
    mods[4]=new mathAddMod(this, 400, 0, 5);
    mods[5]=new valueMod(this, 500, 0, 6);
    mods[6]=new valueMod(this, 600, 0, 7);
    mods[7]=new inspectMod(this, 700, 0, 8);
    mods[8]=new inspectMod(this, 0, 100, 9);
    iPs[ip]=new inPort(this, 2, "in");
    updatePorts();
    name="test";
  }
  modContainer(int tempIndex) {
    super(tempIndex);
    mods = new Mod[255];
    isContainer=true;
    ip=0;
    op=0;
    im=16;
    gx=0;
    gy=0;
    mods[0]=new oscInMod(this, 0, 0, 1);
    mods[1]=new oscRemAddrMod(this, 100, 0, 2);
    mods[2]=new mathMulMod(this, 200, 0, 3);
    mods[3]=new oscSetMsgMod(this, 300, 0, 4);
    mods[4]=new valueMod(this, 400, 0, 5);
    mods[5]=new inspectMod(this, 500, 0, 6);
    mods[6]=new valueMod(this, 600, 0, 7);
    mods[7]=new audioOutMod(this, 700, 0, 8);
    mods[8]=new audioGlideMod(this, 0, 100, 9);
    mods[9]=new oscGetMsgMod(this, 700, 0, 10);
    mods[10]=new valueMod(this, 0, 100, 11);
    mods[11]=new audioGainMod(this, 0, 200, 12);
    mods[12]=new feedOutMod(this, 300, 300, 13);
    mods[13]=new oscOutMod(this, 700, 0, 14);
    mods[14]=new oscAddAddrMod(this, 300, 0, 15);
    mods[15]=new feedInMod(this, 400, 400, 16);
   //mods[16]=cp.getMod("oscIn");
    name="root";
  }
  void go() {
    //process all mods
  }
}

